/*
 StreamRipperX

 Copyright (c) 2002  Wai Hung (Simon) Liu

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import "OLDataSource.h"
#import "ImageAndTextCell.h"
#import "NSString_Extensions.h"
#import "NSOutlineView_Extensions.h"

//
// OLDataSource
//
// I am the data source and delegate of an outlineview
// The AppController class holds a reference to me
// I make sure that the menus are updated to (dis)allow operations on the data inside outlineview
// I load/save UserDefaults data (perhaps I shouldn't?)
//

// UserDefaults
#define DEFAULTS_RADIOSTREAMS @"radiostreams"

#define MyPboardType @"MyCustomOutlineViewPBoardType"

// 'url ' - for drag n drop of a single item from iTunes...
// TODO: how to handle multiple items...
#define ITUNESPboardType @"CorePasteboardFlavorType 0x75726C20"
#define ITUNESNamePboardType @"CorePasteboardFlavorType 0x75726C6E"

#define COLUMNID_NAME @"itemName"
#define COLUMNID_URL @"itemURL"

// GPL icons... Gnome icons (if i remember correctly...)

#define GROUPICON @"local-16folder-evolution.png"
#define STREAMICON @"stream.png"
#define SONGICON @"song.png"
#define RIPPINGICON @"16_loopback.png"


@implementation OLDataSource

// FIXME: This method only here to allow a hack - to select node (current track) in outlineview
- (NSOutlineView *)outlineView { return outlineView; }

- init
{
    [super init];

    rootNode = [[Node alloc] init];
    [rootNode setItemName: @"Root"];
    [rootNode setNodeType:NODE_GROUP];
    return self;
}


- (void)dealloc
{
    [rootNode release];
    [super dealloc];
}



- (void)awakeFromNib {
    NSTableColumn *tableColumn = nil;
    ImageAndTextCell *imageAndTextCell = nil;
    NSCell *cell = nil;
    

    // Insert custom cell types into the table view
    tableColumn = [outlineView tableColumnWithIdentifier: COLUMNID_NAME];
    imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
    [imageAndTextCell setEditable: YES];
    [imageAndTextCell setFont:[NSFont systemFontOfSize:11]];
    [tableColumn setDataCell:imageAndTextCell];

    tableColumn = [outlineView tableColumnWithIdentifier: COLUMNID_URL];
    cell = [tableColumn dataCell];
    [cell setFont:[NSFont systemFontOfSize:11]];
    [tableColumn setDataCell: cell ];
        

    
    // FIXME: This doesn't work... sigh?!
//    [outlineView setDrawsGrid:YES];
//    [outlineView setGridColor:[NSColor blackColor]];
    
    // DRAGGING - Register dragging pboard types
    [outlineView registerForDraggedTypes:
        [NSArray arrayWithObjects:MyPboardType, NSStringPboardType, NSFilenamesPboardType, NSFileContentsPboardType,
            ITUNESPboardType,
            ITUNESNamePboardType,
            //@"ITUN", @"ITUE", @"itun", @"itue", @"Oidl", @"hfs ", @"phfs",
    //        @"CorePasteboardFlavorType 0x75726C20", // 'url '
     //       @"CorePasteboardFlavorType 0x636C666E",
     //       @"CorePasteboardFlavorType 0x54455854",
     //       @"CorePasteboardFlavorType 0x75726C6E",
            
            nil]];
    
    [outlineView setAutosaveName:@"srxOutlineView"];
    [outlineView setAutosaveTableColumns:YES];
}




/*
 * Load/Save outline view data
 */
- (void)loadPrefs {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    //    [defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
    //        [NSArchiver archivedDataWithRootObject:rootNode], @"tree", nil]];

    if ([defaults objectForKey:DEFAULTS_RADIOSTREAMS]) {
        [rootNode release];
        rootNode = [NSUnarchiver unarchiveObjectWithData: [defaults objectForKey:DEFAULTS_RADIOSTREAMS]];
        [rootNode retain];

    }
    else {
        // Load the default prefs file in resources : radiostreams.plist
        // [nb:generated by copying the defaults .plist entry for radiostreams data]
        NSString *path;
        NSDictionary *dict;
        if (path = [[NSBundle mainBundle] pathForResource:DEFAULTS_RADIOSTREAMS ofType:@"plist"]) {
            dict = [NSDictionary dictionaryWithContentsOfFile:path];
            [rootNode release];
            rootNode = [NSUnarchiver unarchiveObjectWithData: [dict objectForKey:DEFAULTS_RADIOSTREAMS]];
            [rootNode retain];
        }
    }
    [outlineView reloadData]; // refresh display
}


- (void)savePrefs {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:[NSArchiver archivedDataWithRootObject:rootNode]
                 forKey:DEFAULTS_RADIOSTREAMS];
    [defaults synchronize];
}



/*
 Methods invoked by AppController
 */
/*
- (NSString *) selectedURL {
    Node *n = [outlineView selectedItem];
    if (n) return [n itemURL];
    return @"";
}
*/

- (Node *)selectedNode {
    return [outlineView selectedItem];
}

- (void)reloadNode:(Node *)node {
    // note: what if node has been removed from the outlineview?
    // it will ignore it?
    [outlineView reloadItem:node reloadChildren:YES];
}




//
// ACTION METHODS
//

/*
 identify nodes to delete
 get the minimum node cover
 removed node if it is being ripped, from the list of nodes to delete
 the only reference is held by the array of children
 remove it from its parent's list of children (thereby removing reference)
 */

- (IBAction)deleteNodes:(id)sender {
    if ([[outlineView allSelectedItems] count] == 0) return;

NSBeginAlertSheet(NSLocalizedString(@"Delete",nil),
                  NSLocalizedString(@"OK",nil),
                  NSLocalizedString(@"Cancel",nil),
                  nil, [NSApp mainWindow], self, nil, //
                  @selector(endDeleteAlertSheet:returnCode:contextInfo:),
                  nil,
                  NSLocalizedString(@"SureDelete",nil) );
// callback will delete the nodes...
}

- (void)endDeleteAlertSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    if (returnCode == NSAlertDefaultReturn) {
        NSArray *items = [outlineView allSelectedItems];
        [items makeObjectsPerformSelector: @selector(removeFromParent)];
        [outlineView deselectAll:nil];
        [outlineView reloadData];
    }
    //NSAlertAlternateReturn
}




#define addchildtarget \
[outlineView selectedRow] < 0 ? rootNode : [outlineView itemAtRow:[outlineView selectedRow]]


- (IBAction)addStream:(id)sender {
    if (![self addChild:NODE_STREAM
                 parent:addchildtarget
                   name:@"Stream"
                    URL:@"URL"])
        NSBeep();
}

- (IBAction)addGroup:(id)sender {
    if (![self addChild:NODE_GROUP
                 parent:addchildtarget
                   name:@"Folder"
                    URL:@""])
        NSBeep();
}

/*
- (IBAction)addSong:(id)sender {
    if (![self addChild:NODE_SONG])
        NSBeep();
//    [self addChild:NODE_SONG];
}
*/


- (BOOL)addChild:(int)nodeType
            name:(NSString *)name
             URL:(NSString *)URL
{
    return [self addChild:nodeType
                   parent:rootNode
                     name:name
                      URL:URL];
}

- (BOOL)addChild:(int)nodeType
          parent:(Node*)parent
            name:(NSString *)name
             URL:(NSString *)URL
{
    int parentNodeType = [parent nodeType];
    Node* newNode;
    
    // check to see if we can create the node
    if ( parentNodeType==NODE_SONG ||
         nodeType==NODE_GROUP && parentNodeType!=NODE_GROUP ||
         nodeType==NODE_STREAM && parentNodeType!=NODE_GROUP ||
         nodeType==NODE_SONG && parentNodeType!=NODE_STREAM ) {
        return NO;
    }

    // Create a new child node
    newNode =  [[Node alloc] init];
    [newNode setItemName:name];
    [newNode setItemURL:URL];

    // set the type
    [newNode setNodeType:nodeType];
    
    [parent addChild: newNode];
    
    // Parent node is retaining the new node
    [newNode release];


    [outlineView reloadData];
    // Expand the item we just added a child to
    [outlineView expandItem:parent];
    // Force display to scroll to the new node
    [outlineView scrollRowToVisible:[outlineView rowForItem:newNode]];
    // Select the node to highlight it
    [outlineView selectRow:[outlineView rowForItem:newNode] byExtendingSelection:NO];
    return YES;
}






// =======================================================================================
// Data source methods
// =======================================================================================

// This method is called repeatedly when the table view is displaying it self. 
- (id)outlineView:(NSOutlineView *)ov child:(int)index ofItem:(id)item
{
    // is the parent non-nil?
    if (item)
        // Return the child
        return [item childAtIndex:index];
    else 
        // Else return the root
//        return rootNode;
        return [rootNode childAtIndex:index];
}




// Called repeatedly to find out if there should be an
// "expand triangle" next to the label
- (BOOL)outlineView:(NSOutlineView *)ov isItemExpandable:(id)item
{
    // Returns YES if the node has children
    return [item expandable];
}





// Called repeatedly when the table view is displaying itself
- (int)outlineView:(NSOutlineView *)ov numberOfChildrenOfItem:(id)item
{
    if (item == nil) {
        // The root object;
//        return 1;
        return [rootNode childrenCount];
    }
    return [item childrenCount];
}






// This method gets called repeatedly when the outline view is trying
// to display it self.

- (id)outlineView:(NSOutlineView *)ov 
    objectValueForTableColumn:(NSTableColumn *)tableColumn 
    byItem:(id)item
{
    // I set the identifier of the columns in IB's inspector
    NSString *identifier = [tableColumn identifier];
    
    // What is returned depends upon which column it is
    // going to appear.
    if ([identifier isEqual:@"itemURL"]){
        return [item itemURL];
    } else {
        return [item itemName];
    }
}





// This method gets called when the user edits the field.

- (void)outlineView:(NSOutlineView *)ov 	
    setObjectValue:(id)object 
    forTableColumn:(NSTableColumn *)tableColumn 
    byItem:(id)item
{

    NSString *identifier = [tableColumn identifier];    

    if ([identifier isEqual:@"itemURL"]) {
            [item setItemURL:object];
    }
    else {
        [item setItemName:object];
    }
}




// =======================================================================================
// NSOutlineView Delegate Methods
// =======================================================================================

/*
 Allow editing of the following fields
         Name  URL
 Folder: Y      N
 Stream: Y      N
 Song  : N      N
*/
- (BOOL)outlineView:(NSOutlineView *)olv
shouldEditTableColumn:(NSTableColumn *)tableColumn
               item:(id)item
{
    NSString *identifier = [tableColumn identifier];
    if ([identifier isEqual:COLUMNID_URL])
        return ( [item nodeType]==NODE_STREAM );
    return ( [item nodeType] != NODE_SONG );
}


- (void)outlineView:(NSOutlineView *)olv
    willDisplayCell:(NSCell *)cell
     forTableColumn:(NSTableColumn *)tableColumn
               item:(id)item
{
    if ([[tableColumn identifier] isEqualToString: COLUMNID_NAME]) {
        NSString *icon;
        switch ([item nodeType]) {
            case NODE_GROUP:
                icon = GROUPICON;
                break;
            case NODE_STREAM:
                if ([item ripping]) {
                    icon = RIPPINGICON;
                }
                else {
                    icon = STREAMICON;
                }
                break;
            case NODE_SONG:
                if ([item ripping]) {
                    icon = RIPPINGICON;
                }
                else {
                    icon = SONGICON;
                }
                break;
            default:
                break;
        }
        [cell setImage: [NSImage imageNamed:icon]];
    }

    /*
    {
        int rowIndex = [olv rowForItem:item];

        if ( [cell respondsToSelector:@selector(setBackgroundColor:)] )
        {
            if ([olv isRowSelected:rowIndex]) {
                [cell setBackgroundColor: [NSColor selectedControlColor]];
            }
            else {
                [cell setBackgroundColor: (rowIndex % 2 == 0) ? [NSColor colorWithDeviceRed:0.95 green:0.95 blue:1.0 alpha:1.0] : [NSColor whiteColor]];
            }
            [cell setDrawsBackground:YES];
        }
    }
     */

}




/*
- (void)outlineView:(NSOutlineView *)olv willDisplayOutlineCell:(id)cell
     forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    int rowIndex = [olv rowForItem:item];

    if ( [cell respondsToSelector:@selector(setBackgroundColor:)] )
    {
        [cell setBackgroundColor: (rowIndex % 2 == 0) ? [NSColor lightGrayColor] : [NSColor whiteColor]];
        [cell setDrawsBackground:YES];
    }
}
*/

// =======================================================================================
// Target/Action method
// =======================================================================================

// When the user selects / double-clicks on an entry.
// Action could be to edit the tags / fields of the entry in a separate panel
- (IBAction)outlineViewAction:(id)sender {
    /*
    int selectedRow = [outlineView selectedRow];
    Node *selectedItem;

    // Is nothing selected?
    if (selectedRow < 0) {
        //NSLog(@"Nothing selected");
        return;
    }

    // Which node is that?
    selectedItem  = [outlineView itemAtRow:selectedRow];
*/
     
    // TODO: Delete the code below
/*
    [selectionTextField setStringValue:
        [NSString stringWithFormat:@"node name=%@, parent node name=%@",
            [selectedItem itemName],
            [[selectedItem parent] itemName]]];
*/
}


// ================================================================
//  NSOutlineView data source methods. (dragging related)
// ================================================================

- (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard {
    int count;
    items = [Node minimumNodeCoverFromNodesInArray: items];
    // Lets get the minimum cover first, to eliminate any selected "songs" which are children
    // of "groups" being dragged
    // MY RULE: if any one of the remaining items is a song then we CANNOT drag
    //          That is, songs selected without their parent group being selected
    count = [items count];
    while (--count >= 0) {
        if ( [[items objectAtIndex:count ] nodeType] == NODE_SONG )
            return NO;
    }


    draggedNodes = items; // Don't retain since this is just holding temporaral drag information, and it is only used during a drag!  We could put this in the pboard actually.

    // Declare the type of drag that we are initiating
    // NOTE: It does not make sense to drag entries outside so we should not declare StringType pboard
    // Provide data for our custom type, and simple NSStrings.
     [pboard declareTypes:[NSArray arrayWithObjects: MyPboardType, nil] owner:self];

     // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!.
     [pboard setData:[NSData data] forType:MyPboardType];

     // NOTE: This does not make sense for us, so we should not do it!
     // Put string data on the pboard... can drag into TextEdit
     //[pboard setString: @"dummy stuff" forType: NSStringPboardType];
     return YES;
 }






// This method validates whether or not the proposal is a valid one. Returns NO if the drop should not be allowed.
// In awakeFromNib: we have already registered to allow dropping of a custom format (internal), NSStrings...
// If internal, we can move nodes around, subject to rules
// If external, e.g. string, maybe retarget depending if http://....pls or not
- (unsigned int)outlineView:(NSOutlineView*)olv
               validateDrop:(id <NSDraggingInfo>)info
               proposedItem:(id)item
         proposedChildIndex:(int)childIndex
{
    //Node *targetNode = item;
    BOOL isValidTarget = YES;
    // is on drop = yes if dropping on top of item, or on entire outline view
    // Default behaviour of drag and drop
    // - cannot drop item on itself
    // - can drop item on entire outline view
    //BOOL isOnDrop = (childIndex==NSOutlineViewDropOnItemIndex);
    BOOL isInternal = ( [info draggingSource]==outlineView) && ([[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject: MyPboardType]] != nil );


    //
//    NSLog(@"pboard types = %@", [[info draggingPasteboard] types]);
 //   NSLog(@"general pboard types = %@", [[NSPasteboard generalPasteboard] types]);
    //

    
    //NSLog(@"childIndex=%d, droponitemindex=%d", childIndex, NSOutlineViewDropOnItemIndex);

    // THIS IS AN EXAMPLE OF RETARGETING THE TARGET NODE AND THE CHILD INDEX TO BE "ON DROP"
    //     if ([self onlyAcceptDropOnRoot]) {
    //         targetNode = nil;
    //         childIndex = NSOutlineViewDropOnItemIndex;
    //     } else {


        // Is the source internal, or external ?


            // MY RULE: Do not drop on songs OR streams (only groups allowed)
            if ( [item nodeType] != NODE_GROUP )
                isValidTarget = NO;
        if (isInternal && isValidTarget) {
            // COMMON RULE: Do not drop onto descendant of any dragged items!!!
            isValidTarget = ![item isDescendantOfNodeInArray: draggedNodes];
        }
//        else {
            // if external, two types of things being dragged in
            // 1. string with URL (can only go in folder)
            // 2. string without URL - this is a folder, can only go in FOLDER
            // 3. .PLS file (this to be implemented LATER, with option 1 perhaps reusing code if
            //    url poiints to a  PLS file) - CAN ONLY GO IN FOLDER
            // ---> whether internal or external, can only go in folder!!!
//        }
        

        // Example code: retrieves dragged nodes from draggingSource...
    //    NSArray *_draggedNodes = [[[info draggingSource] dataSource] draggedNodes];

        // Set the drop item and child index - only if we retargeted the dest node.
    //
    //[outlineView setDropItem:targetNode dropChildIndex:childIndex];
    
    return isValidTarget ? NSDragOperationGeneric : NSDragOperationNone;
}







- (BOOL)outlineView:(NSOutlineView*)olv acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(int)childIndex {

    NSPasteboard * pboard = [info draggingPasteboard];
    BOOL isOnDrop = (childIndex==NSOutlineViewDropOnItemIndex);

    NSMutableArray *itemsToSelect = nil;




    
    // If target is NIL, then we need to retarget to ROOT NODE
    if (targetItem==nil) {
        targetItem = rootNode;
    }

    //NSLog(@"dragging source=%@", [info draggingSource ]);
    // custom pboard type
    // INTERNAL DROP
    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:MyPboardType, nil]] != nil) {
        //        NSLog(@"Internal drop");
        //        NSArray *_draggedNodes = [TreeNode minimumNodeCoverFromNodesInArray: [dragDataSource draggedNodes]];
        NSEnumerator *draggedNodesEnum = [draggedNodes objectEnumerator];
        Node *draggedNode = nil, *draggedNodeParent = nil;
        //        itemsToSelect = [NSMutableArray arrayWithArray:[self selectedNodes]];
        //        draggedNodes = [Node minimumNodeCoverFromNodesInArray: draggedNodes];

        // if we are dragging nodes amongst themselves, then we have to update the childIndex
        // e.g. nodes ABC, move A to after C
        // We check all dragged nodes, update the childIndex if necessary, before removing node
        while ( draggedNode = [draggedNodesEnum nextObject] ) {
            draggedNodeParent = [draggedNode parent];
            if (targetItem==draggedNodeParent && [targetItem indexOfChild: draggedNode]<childIndex)
                childIndex--;
            [draggedNodeParent removeChild: draggedNode];
        }
        // FIXME: ONDROP
        if (isOnDrop) {
            [targetItem addChildren: draggedNodes];
        } else {
            [targetItem insertChildren: draggedNodes atIndex: childIndex];
        }

        itemsToSelect = [NSMutableArray arrayWithArray:draggedNodes];
    }
    // EXTERNAL DROP
    // TODO: Take action if .PLS file dropped (e.g. from Finder)
    // stringForType - returns the XML propertylist
    // propertyListForType - returns an array of strings, where each string is a filename

    // 27 Oct 2002
    // Behaviour: If we drop a .pls file, we parse it immediately for File1.
    // If we drop a .pls link, we should not parse the URL immediately as the underlying
    // station IP and Port is probably dynamic, whereas the URL is more likely to be static
    else if ([pboard availableTypeFromArray:[NSArray arrayWithObject: NSFilenamesPboardType]]) {
        NSArray *files = [pboard propertyListForType: NSFilenamesPboardType];
        NSEnumerator *enumerator = [files objectEnumerator];
        id filename;
        NSString *contents, *url, *name;
        NSMutableArray *tmpArray = [NSMutableArray arrayWithCapacity:0];
        Node *newNode;
        while (filename = [enumerator nextObject]) {
            if ([[[filename pathExtension] lowercaseString] isEqualToString:@"pls"]) {
//                url = [self getFile1FromPLS:[NSString stringWithContentsOfFile:filename]];
                contents = [NSString stringWithContentsOfFile:filename];
                url = [contents getValueForKey:@"File1"];
                name = [contents getValueForKey:@"Title1"];
                if (url) {
                    newNode =  [[Node alloc] init];
                    [newNode setItemName:(name) ? name : url]; // TODO: check this works
                    [newNode setItemURL:url];
                    [newNode setNodeType:NODE_STREAM];
                    [tmpArray addObject: newNode];
                    [newNode release];
                }
            }
        }
            
        itemsToSelect = [NSMutableArray arrayWithArray:tmpArray];

	}

        // handle ITUNES and URL drag n drop
        else if ([pboard availableTypeFromArray:[NSArray arrayWithObject: ITUNESPboardType]]) {
            NSString *string = [pboard stringForType: ITUNESPboardType];
            NSString *name = [pboard stringForType:ITUNESNamePboardType];
#ifdef DEBUG
            NSLog(@"dragndrop: itunes string = %@", string);
            NSLog(@"dragndrop: itunes plist = %@", [pboard propertyListForType:ITUNESPboardType]);
            NSLog(@"dragndrop: itunes name string = %@", name);
#endif
/*
            if ( [string hasPrefix:@"http://pri.kts-af.net/redir/index.pls?"] ||
                 [string hasPrefix:@"http://www.shoutcast.com/sbin/shoutcast"] // -playlist.pls?"]
                 ) {
    string = [[string getURLWithCurl] getValueForKey:@"File1"];
            }
*/
            if (string) {
                Node *newNode =  [[Node alloc] init];
                [newNode setItemName:name];
                [newNode setItemURL:string];
                [newNode setNodeType:NODE_STREAM];
                itemsToSelect = [NSMutableArray arrayWithObject: newNode];
                [newNode release];
            }
            else {
#ifdef DEBUG
                NSLog(@"%s : failed to parse URL from iTunes", __PRETTY_FUNCTION__ );
#endif
            }
            // FIXME: What to do if nil?  Display an error alert sheet?
        }
    // Drop a text string containing a URL to a .pls file
    else if ([pboard availableTypeFromArray:[NSArray arrayWithObject: NSStringPboardType]]) {
  //      NSString *contents, *name;
        NSString *url = [pboard stringForType: NSStringPboardType];

/*
        if ([[url lowercaseString] hasPrefix:@"http://"] &&
            [[url lowercaseString] hasSuffix:@".pls"]) {
            
            contents = [url getURLWithCurl];
            url = [contents getValueForKey:@"File1"];
            name = [contents getValueForKey:@"Title1"];
            //string = [[string getWithCurl] getValueForKey:@"File1"];
//            string = [self getFile1FromPLS:
//                [self getWithCurl:string]];
        }
 */
        if (url) {
            Node *newNode =  [[Node alloc] init];
            [newNode setItemName:url];
            [newNode setItemURL:url];
            [newNode setNodeType:NODE_STREAM];
            itemsToSelect = [NSMutableArray arrayWithObject: newNode];
            [newNode release];
        }
    }



// Insert the new nodes IFF not internal drag'n'drop
    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:MyPboardType, nil]] == nil) {
        NSEnumerator *enumerator = [itemsToSelect objectEnumerator];
        id node;
        while ((node = [enumerator nextObject])) {
             if (isOnDrop) {
                [targetItem addChild: node ];
            } else {
                [targetItem insertChild: node atIndex:childIndex ];
            }
        }
    }


    
    [outlineView reloadData];
    
    // Note: We kept a list of the inserted/new nodes, so we can make sure they are selected
    // or at least shown in their new position
    [outlineView expandItem:targetItem];
    [outlineView selectItems: itemsToSelect byExtendingSelection: NO];
    
    // TODO: Jump to the selected items in the display
    return YES;
}



/*
 Updating menus
 - if nothing selected, we can create group or stream in root node
 - if something selected, we can only create a group or stream on a group node
 - delete only if something selected
 */
-(BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    SEL sel = [menuItem action];
    if ( sel == @selector(addGroup:)) {
        if ([outlineView selectedItem])
            return ([[outlineView selectedItem] nodeType] == NODE_GROUP);
    }
    else if ( sel == @selector(addStream:)) {
        if ([outlineView selectedItem])
            return ([[outlineView selectedItem] nodeType] == NODE_GROUP);
    }
    else if ( sel == @selector(deleteNodes:)) {
        return ([[outlineView allSelectedItems] count] > 0);
    }
    return YES;
}



/*
 * Helper method - get File1 value from a .pls file (represented as a string)
 */
/*
-(NSString *)getFile1FromPLS: (NSString *)pls {
    int tmp;   
    NSRange range = [pls rangeOfString:@"File1=http"];
#ifdef DEBUG
    NSLog(@"%s", __PRETTY_FUNCTION__);
    NSLog(@"pls file = %@", pls);
#endif
    if ( ! [ pls hasPrefix:@"[playlist]"] )
        return nil;
    
    if (range.location != NSNotFound) {
        tmp = range.location+6;
        range = [[pls substringFromIndex:tmp] rangeOfString:@"\n"];
        if (range.location != NSNotFound) {
            range.length = range.location;
            range.location = tmp;
            return [pls substringWithRange:range];
        }
    }
    return nil;
}    
*/

/*
 * Create a NSTask to invoke "curl -sL "http://..."
 * -L flag tells curl to follow redirections
 * -s silent
 * Read stdout
 */
/*
- (NSString *)getWithCurl:(NSString *)url
{
    NSTask *task;
    NSFileHandle *fromCurl;
    NSPipe *fromPipe;
    NSString *result;

#ifdef DEBUG
    NSLog(@"url = %@", url);
#endif
    
    fromPipe = [NSPipe pipe];
    fromCurl = [fromPipe fileHandleForReading];

    task = [[NSTask alloc] init];
    [task setLaunchPath:@"/usr/bin/curl"];
    [task setArguments:
        [NSArray arrayWithObjects:@"-sL", url, nil]];
    [task setStandardOutput:fromPipe];
    [task launch];

    result = [[NSString alloc] initWithData:[fromCurl readDataToEndOfFile]
                                 encoding:NSASCIIStringEncoding];
#ifdef DEBUG
    NSLog(@"output from curl = \n%@", result);
#endif

    [fromCurl closeFile];
    [task release];
    return result;
}
*/

/*
 * Experimental function
 *
 * Assess user feedback, and comments w/regards to pulling stations off Shoutcast
 *
 * http://www.shoutcast.com/directory/index.phtml?sgenre=...
 * if genre title has ' ' it is URL encoded %20
 *       <OPTION VALUE="TopTen">-=[Top 25 Streams]=-
	<OPTION VALUE="Alternative">Alternative
 <OPTION VALUE="College"> - College
 <OPTION VALUE="Industrial"> - Industrial
 <OPTION VALUE="Punk"> - Punk
 <OPTION VALUE="Hardcore"> - Hardcore
 <OPTION VALUE="Ska"> - Ska
	<OPTION VALUE="Americana">Americana
 <OPTION VALUE="Blues"> - Blues
 <OPTION VALUE="Folk"> - Folk
 <OPTION VALUE="Cajun"> - Cajun
 <OPTION VALUE="Bluegrass"> - Bluegrass
	<OPTION VALUE="Classical">Classical
 <OPTION VALUE="Contemporary"> - Contemporary
 <OPTION VALUE="Opera"> - Opera
 <OPTION VALUE="Symphonic"> - Symphonic
	<OPTION VALUE="Country">Country
 <OPTION VALUE="Western Swing"> - Western Swing
 <OPTION VALUE="New Country"> - New Country
 <OPTION VALUE="Bluegrass"> - Bluegrass
	<OPTION VALUE="Electronic">Electronic
 <OPTION VALUE="Ambient"> - Ambient
 <OPTION VALUE="Drum and Bass"> - Drum and Bass
 <OPTION VALUE="Trance"> - Trance
 <OPTION VALUE="Techno"> - Techno
 <OPTION VALUE="House"> - House
 <OPTION VALUE="Downtempo"> - Downtempo
 <OPTION VALUE="Breakbeat"> - Breakbeat
 <OPTION VALUE="Acid Jazz"> - Acid Jazz
	<OPTION VALUE="Hip%20Hop">Hip-Hop/Rap
 <OPTION VALUE="Hardcore"> - Hardcore
 <OPTION VALUE="Alternative"> - Alternative
 <OPTION VALUE="Turntablism"> - Turntablism
 <OPTION VALUE="Old School"> - Old School
 <OPTION VALUE="New School"> - New School
	<OPTION VALUE="Jazz">Jazz
 <OPTION VALUE="Latin"> - Latin
 <OPTION VALUE="Swing"> - Swing
 <OPTION VALUE="Big Band"> - Big Band
 <OPTION VALUE="Classic"> - Classic
 <OPTION VALUE="Smooth"> - Smooth
 <OPTION VALUE="Acid Jazz"> - Acid Jazz
	<OPTION VALUE="Pop/Rock">Pop/Rock
 <OPTION VALUE="Oldies"> - Oldies
 <OPTION VALUE="Classic"> - Classic
 <OPTION VALUE="80s"> - 80s
 <OPTION VALUE="Top 40"> - Top 40
 <OPTION VALUE="Metal"> - Metal
 <OPTION VALUE="Rock"> - Rock
 <OPTION VALUE="Pop"> - Pop
	<OPTION VALUE="R%26B/Soul">R&B/Soul
 <OPTION VALUE="Contemporary"> - Contemporary
 <OPTION VALUE="Classic"> - Classic
 <OPTION VALUE="Funk"> - Funk
 <OPTION VALUE="Smooth"> - Smooth
 <OPTION VALUE="Urban"> - Urban
	<OPTION VALUE="Spiritual">Spiritual
 <OPTION VALUE="Pop"> - Pop
 <OPTION VALUE="Rock"> - Rock
 <OPTION VALUE="Alternative"> - Alternative
 <OPTION VALUE="Gospel"> - Gospel
 <OPTION VALUE="Country"> - Country
	<OPTION VALUE="Spoken">Spoken
 <OPTION VALUE="Talk"> - Talk
 <OPTION VALUE="Comedy"> - Comedy
 <OPTION VALUE="Spoken Word"> - Spoken Word
	<OPTION VALUE="World">World
 <OPTION VALUE="Reggae"> - Reggae/Island
 <OPTION VALUE="African"> - African
 <OPTION VALUE="Latin"> - Latin
 <OPTION VALUE="European"> - European
 <OPTION VALUE="Middle Eastern"> - Middle Eastern
 <OPTION VALUE="Asian"> - Asian
	<OPTION VALUE="Other/Mixed">Other/Mixed
 <OPTION VALUE="Eclectic"> - Eclectic
 <OPTION VALUE="Film"> - Film/Show
 <OPTION VALUE="Instrumental"> - Instrumental
 */
- (IBAction)importFromShoutCast:(id)sender {
    Node *folderNode, *newNode;
    NSString *genre, *name, *url;
    NSArray *lines;

    NSTask *task;
    NSFileHandle *fromFH, *toFH;
    NSPipe *fromPipe, *toPipe;
    NSString *result;
    const char *input;

    if (![sender isKindOfClass:[NSMenuItem class]]) return;  // unlikely...
    genre = [sender title];

#ifdef DEBUG
    NSLog(@"menuitem title = %@", genre);
#endif    

    if ([genre isEqualToString:@"General"])
        url = @"http://www.shoutcast.com";
    else
        url = [[@"http://www.shoutcast.com/directory/index.phtml?sgenre=" 			stringByAppendingString:genre] escapeSpaceInURL];
    
    /* get main web page if "General"*/
    input = [[url getURLWithCurl] UTF8String];
    if (!input) return; // fail

#ifdef DEBUG
    NSLog(@"Received %d bytes of data", strlen(input));
#endif
    
    fromPipe = [NSPipe pipe];
    fromFH = [fromPipe fileHandleForReading];
    toPipe = [NSPipe pipe];
    toFH = [toPipe fileHandleForWriting];

    task = [[NSTask alloc] init];
//    [task setLaunchPath:@"/usr/bin/cat"];
//    [task setArguments:nil];
 
    [task setLaunchPath:@"/usr/bin/perl"];
    [task setArguments:
        [NSArray arrayWithObjects:
            [[NSBundle mainBundle] pathForResource:@"ParseShoutcastWWW" ofType:@"pl"],
            nil]];

 [task setStandardInput:toPipe];
    [task setStandardOutput:fromPipe];
    [task launch];
    
    [toFH writeData:[NSData dataWithBytes:input length:strlen(input)]];

#ifdef DEBUG
    NSLog(@"Data written to STDIN for Perl process");
#endif
    
        [toFH closeFile];

    result = [[NSString alloc] initWithData:[fromFH readDataToEndOfFile]
                                   encoding:NSASCIIStringEncoding];
#ifdef DEBUG
    NSLog(@"Output from Perl script = \n%@", result);
#endif

    [fromFH closeFile];
    [task release];

    
    if (!result) return;
    
    lines = [result componentsSeparatedByString:@"\n"];

    // If there are imported stations, then insert under a group name    
    folderNode = [[Node alloc] init];
    [folderNode setItemName:
         [NSString stringWithFormat:@"ShoutCast - %@ - %@", genre,
        [[NSDate date] descriptionWithLocale:[NSLocale currentLocale]]]];

    [folderNode setItemURL:@""];
    [folderNode setNodeType:NODE_GROUP];
    [rootNode addChild:folderNode];

    {
        NSEnumerator *e = [lines objectEnumerator];
        id obj;
        while ( (obj = [e nextObject]) ) {
/*            url = [self getFile1FromPLS:                [self getWithCurl:obj]];;
*/
            url = obj;
            name = [e nextObject];
            if (!(url && name)) break;
            newNode =  [[Node alloc] init];
            [newNode setItemName:name];
            [newNode setItemURL:url];
            [newNode setNodeType:NODE_STREAM];
            [folderNode addChild:newNode];
            [newNode release];
        }
    }
    
    // Loop through results
    /*
    */
    [outlineView reloadData];
    [outlineView expandItem:folderNode];
    [folderNode release];
}

@end
